/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.xi8ix.gwtc;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 * Goal which touches a timestamp file.
 *
 * @goal gwtc
 * @phase compile
 */
public class GwtcMojo extends AbstractMojo {

    /**
     * Allows running the compiler in a separate process.
     * If "false" it uses the built in compiler, while if "true" it will use an executable.
     *
     * @parameter default-value="false"
     */
    private boolean fork = false;
    
    /**
     * Indicates whether the build will continue even if there are compilation errors; defaults to true.
     *
     * @parameter expression="${maven.compiler.failOnError}" default-value="true"
     */
    private boolean failOnError = true;

    /**
     * Set to true to include debugging information in the compiled class files.
     *
     * @parameter expression="${maven.compiler.debug}" default-value="true"
     */
    private boolean debug = true;

    /**
     * Set to true to show messages about what the compiler is doing.
     *
     * @parameter expression="${maven.compiler.verbose}" default-value="false"
     */
    private boolean verbose = false;

    /**
     * Sets whether to show source locations where deprecated APIs are used.
     *
     * @parameter expression="${maven.compiler.showDeprecation}" default-value="false"
     */
    private boolean showDeprecation = false;

    /**
     * Set to true to optimize the compiled code using the compiler's optimization methods.
     *
     * @parameter expression="${maven.compiler.optimize}" default-value="false"
     */
    private boolean optimize = false;

    /**
     * Set to true to show compilation warnings.
     *
     * @parameter expression="${maven.compiler.showWarnings}" default-value="false"
     */
    private boolean showWarnings = false;

    /**
     * The source directories containing the sources to be compiled. For maven this is <tt>src/main/java</tt>
     *
     * @parameter expression="${project.compileSourceRoots}"
     * @required
     * @readonly
     */
    private List compileSourceRoots = null;

    /**
     * Project classpath.
     *
     * @parameter expression="${project.compileClasspathElements}"
     * @required
     * @readonly
     */
    private List classpathElements = null;

    /**
     * The directory where the webapp is built. For maven this is <tt>target/artifactId-version</tt>.
     *
     * @parameter expression="${project.build.directory}/${project.build.finalName}"
     * @required
     */
    private File webappDirectory = null;

    /**
     * The directory for compiled classes. For maven this is <tt>target/classes</tt>.
     *
     * @parameter expression="${project.build.outputDirectory}"
     * @required
     * @readonly
     */
    private File outputDirectory = null;

    /**
     * Location of the build. For maven this is <tt>target</tt>.
     *
     * @parameter expression="${project.build.directory}"
     * @required
     */
    private File buildDirectory = null;

    /**
     * Optional additional JVM arguments that should be passed to the "java" command.
     *
     * @parameter
     */
    private String jvmArgs = null;

    /**
     *  Logging options should be one of: ERROR, WARN, INFO, TRACE, DEBUG, SPAM, or ALL.
     *
     * @parameter
     */
    private String gwtLog = null;

    /**
     * Specify a directory for generated files are written for review or omit to ignore.
     *
     * @parameter
     */
    private File gwtGen = null;

    /**
     * Indicate if logs should be output in a graphical tree view.
     *
     * @parameter default-value="false"
     */
    private boolean gwtTreeLogger;

    /**
     * Indicate the output style for javascript: OBF[USCATED], PRETTY, or DETAILED.
     * Defaults to OBF.
     *
     * @parameter
     */
    private String gwtOutputStyle;

    /**
     * Indicate if compiled output should check assert statements.
     *
     * @parameter default-value="false"
     */
    private boolean gwtAssert;

    /**
     * Indicate if all souce code should be validated but not compiled.
     *
     * @parameter default-value="false"
     */
    private boolean gwtValidateOnly;

    /**
     * The list of GWT modules to compile.
     *
     * @parameter
     * @required
     */
    private List modules = null;

    private static final String JAR_NAME = "gwtc-1.5.jar";

    public void execute() throws MojoExecutionException {
        if (getLog().isDebugEnabled()) {
            getLog().debug("fork: " + fork);
            getLog().debug("failOnError: " + failOnError);
            getLog().debug("debug: " + debug);
            getLog().debug("verbose: " + verbose);
            getLog().debug("showDep: " + showDeprecation);
            getLog().debug("opt: " + optimize);
            getLog().debug("showWarn: " + showWarnings);
            getLog().debug("out: " + outputDirectory);
            getLog().debug("build: " + buildDirectory);
            getLog().debug("web: " + webappDirectory);
            getLog().debug("jvmArgs: " + jvmArgs);
            getLog().debug("gwtLog: " + gwtLog);
            getLog().debug("gwtGen: " + gwtGen);
            getLog().debug("gwtTreeLogger: " + gwtTreeLogger);
            getLog().debug("gwtOutputStyle: " + gwtOutputStyle);
            getLog().debug("gwtAssert: " + gwtAssert);
            getLog().debug("gwtValidateOnly: " + gwtValidateOnly);
            StringBuffer srcRoots = new StringBuffer("src: ");
            for (Object root: compileSourceRoots){
                srcRoots.append(" ").append(root);
            }
            getLog().debug(srcRoots.toString());

            StringBuffer classpath = new StringBuffer("cp: ");
            for (Object path: classpathElements) {
                classpath.append(" ").append(path);
            }
            getLog().debug(classpath.toString());

            StringBuffer moduleInfo = new StringBuffer("mods: ");
            for (Object module: modules) {
                moduleInfo.append(" ").append(module);
            }
            getLog().debug(moduleInfo.toString());
        }

        File gwtcDir = new File(buildDirectory,"gwtc");
        if (!gwtcDir.exists()) {
            gwtcDir.mkdirs();
            getLog().info("Created directory: " + gwtcDir.getAbsolutePath());
        }
        File gwtc = new File(gwtcDir,JAR_NAME);
        Exception compilerException = null;
        byte buffer [] = new byte[1024];
        if (!gwtc.exists()) {
            FileOutputStream out = null;
            InputStream in = null;
            try {
                out = new FileOutputStream(gwtc);
                in = this.getClass().getClassLoader().getResourceAsStream(JAR_NAME);
                for (int read = in.read(buffer); read != -1; read = in.read(buffer)) {
                    out.write(buffer,0,read);
                }
                getLog().info("Created working gwtc: " + gwtc.getAbsolutePath());
            } catch (Exception e) {
                compilerException = e;
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                    if (out != null) {
                        out.close();
                    }
                } catch (IOException e) {
                    compilerException = e;
                }
            }
        }
        if (compilerException != null) {
            throw new MojoExecutionException("Trouble creating working compiler",compilerException);
        }
        if (!webappDirectory.exists()) {
            webappDirectory.mkdirs();
            getLog().info("Created directory: " + webappDirectory.getAbsolutePath());
        }

        String activeModule = null;
        String classpath;
        StringBuffer cp = new StringBuffer();
        cp.append(gwtc.getAbsolutePath());
        for (Object path: compileSourceRoots) {
            cp.append(File.pathSeparator).append(path);
        }
        for (Object path: classpathElements) {
            cp.append(File.pathSeparator).append(path);
        }
        classpath = cp.toString();
        getLog().debug("CP: "+ classpath);
        
        ArrayList<String> args = new ArrayList<String>();
        args.add("java");
        if (jvmArgs != null) {
            for(String arg: jvmArgs.split("\\s")) {
                arg = arg.trim();
                if (arg.length() > 0) {
                    args.add(arg);
                }
            }
        }
        args.add("-classpath"); args.add(classpath);
        args.add("com.google.gwt.dev.GWTCompiler");
        args.add("-out"); args.add(webappDirectory.getAbsolutePath());

        if (gwtLog != null) {
            gwtLog = gwtLog.trim().toUpperCase();
            if (    "ERROR".equals(gwtLog) ||
                    "WARN".equals(gwtLog) ||
                    "INFO".equals(gwtLog) ||
                    "TRACE".equals(gwtLog) ||
                    "DEBUG".equals(gwtLog) ||
                    "SPAM".equals(gwtLog) ||
                    "ALL".equals(gwtLog)) {
                getLog().debug("logging: " + gwtLog);
            } else {
                gwtLog = null;
            }
            if (gwtLog != null) {
                args.add("-logLevel");
                args.add(gwtLog);
            }
        }
        if (gwtGen != null) {
            args.add("-gen");
            args.add(gwtGen.getPath());
        }
        if (gwtTreeLogger) {
            args.add("-treeLogger");
        }
        if (gwtOutputStyle != null) {
            gwtOutputStyle = gwtOutputStyle.trim().toUpperCase();
            if (    "OBF".equals(gwtOutputStyle) ||
                    "OBFUSCATED".equals(gwtOutputStyle) ||
                    "PRETTY".equals(gwtOutputStyle) ||
                    "DETAILED".equals(gwtOutputStyle)) {
                getLog().debug("style: " + gwtOutputStyle);
            } else {
                gwtOutputStyle = null;
            }
            if (gwtOutputStyle != null) {
                args.add("-style");
                args.add(gwtOutputStyle);
            }
        }
        if (gwtAssert) {
            args.add("-ea");
        }
        if (gwtValidateOnly) {
            args.add("-validateOnly");
        }

        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.directory(gwtcDir);
        try {
            for (Object module: modules) {
                activeModule = module.toString();
                List<String> commands = processBuilder.command();
                commands.clear();
                commands.addAll(args);
                commands.add(activeModule);
                Process process = processBuilder.start();
                InputStream std = process.getInputStream();
                for (int read = std.read(buffer); read != -1; read = std.read(buffer)) {
                    getLog().info(new String(buffer,0,read).trim());
                }
                std = process.getErrorStream();
                for (int read = std.read(buffer); read != -1; read = std.read(buffer)) {
                    getLog().error(new String(buffer,0,read).trim());
                }
                if (process.waitFor() != 0) {
                    throw new MojoExecutionException("Error compiling module " + activeModule);
                }
            }
        } catch (Exception e) {
            throw new MojoExecutionException("Error compiling module " + activeModule, e);
        }
    }
    interface Compiler {
        void compile();
    }
}
